Conversation
Action items
|
|
Pushed Fixed
Answered, no change
222 pass / 2 fail (both pre-existing on |
Adds a sync provider for AIHubMix, an aggregator relaying ~409 LLM routes from 26 labs. Everything is read from the public catalog endpoint — no credentials, no per-model tables in the adapter. GET https://aihubmix.com/api/v1/models?type=llm Relay → lab resolution comes from the endpoint itself: `vendor` names the lab, `variant_of` names the route this entry is a variant of, and lookups walk that chain nearest-first. The hand-maintained `developer_id → lab` table and the prefix/suffix strip list are gone; what remains is `VENDOR_LABS`, four entries mapping namespaces the two registries spell differently. Shape rules, not per-model judgement: - `max_output: 0` and `max_output >= context_length` are read as "unknown" and inherited from the base model rather than published. - A stated limit below an accepted one but at or above 1000³/1024³ is a decimal restatement of a binary window, not a host cap, and resolves to the accepted value. - Effort spellings outside `ReasoningEffortValue` are mapped or dropped; the endpoint's extra `default` key inside `reasoning_options[]` is not part of `ReasoningOption` and is dropped. - A model whose lab entry says `reasoning = true` but which publishes no controls is skipped, never stamped with `reasoning_options = []` — an empty array means "confirmed no caller control", not "not researched". - A relay with a named vendor belongs on `base_model`; it is never authored as a full standalone entry. `deleteMissing` is false and `trackMissingModels` is true: AIHubMix rotates routes in and out, and a transient absence should not delete a catalog entry. Unresolvable relays open deduped `[missing-model]` issues instead of notices nobody acts on. Framework side: `formatToml` emits `input_audio` / `output_audio` inside cost tiers (both are already in `Cost`, but were dropped when writing tiered pricing), and `issueModels` also collects skipped remotes when a provider tracks missing models without skipping creates. Rebased onto dev and squashed from 22 commits; the review-round history lives in the PR thread. Two conflicts resolved by union rather than by taking a side: `issueModels` keeps dev's `missingRemote` + dedupe alongside this branch's `trackMissingModels` condition, and the missing-model issue body keeps dev's wording with the `base_model` case appended. Verification: `bun run validate` exit 0; `bun test packages/core/test/sync.test.ts` 221 pass / 3 fail, the same 3 failing on a clean `origin/dev` checkout (Hyper reasoning inheritance, DeepInfra modalities, and an LLM Gateway case-variant assertion that only fails on case-insensitive filesystems). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`-free`, `-reasoning` and `-non-reasoning` all name a way into a model the listing already carries under its own ID. `-free` is the free-tier route (53 of them, 40 pointing at the paid route through `variant_of`); the two Grok suffixes are the pre-split routes that reach one model with thinking forced on or off, which the catalog states as `reasoning_options` rather than as two entries. Filtered in parseModels rather than translateModel, so a variant is absent from the sync altogether — no file, and no skip notice or missing-model issue asking a human to supply metadata the catalog does not want. The relay catalog keeps every entry, because a variant is still a valid `variant_of` target for a route that does belong here. Removes the four free-tier files already written, and takes the dry run from 129 creates to 106. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5c0590c to
360534f
Compare
Action items
|
The endpoint uses `reasoning` to state the controls this host exposes, so leaving it off is the catalog saying it exposes none. Reading the omission as unknown and falling back to the lab entry's `true` was what left six routes unwritable: the lab says the model reasons, the endpoint publishes no controls for it, and AGENTS.md requires `reasoning_options` whenever `reasoning = true`, so the route could only be skipped. Nine routes resolve differently under this rule — the four chat-tuned snapshots, gemini-2.5-flash-image, the two deprecated MiMo routes, and three Qwen/Solar entries — and none of them has a file today, so nothing already published flips. `tool_call` keeps the older reading, because nothing in the endpoint denies tool use. Takes the dry run from 13 skips to 7, all of which are now routes the endpoint really does flag as reasoning without publishing the tiers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Action items
|
…own" This reverts commit 1eeaa0c. Reading the endpoint's omission as a denial was wrong. The omission is missing data, not a statement: 108 routes leave the flag off and they include `Qwen/QwQ-32B`, which has no non-thinking mode at all, `AiHubmix-Phi-4-mini-reasoning`, `codex-mini-latest`, and the whole `qwen3-*` hybrid-thinking family. A catalog that omits the flag for a model that can only think is not describing what it exposes. The measurement that justified the rule -- nine routes resolve differently and none has a file today -- sized the blast radius, not the claim. A real sync shows what it actually writes: six cards carry `reasoning = false` against a lab entry that says `true`, and for `mimo-v2-omni` / `mimo-v2-pro` that makes this the only provider in the repo denying that MiMo v2 reasons, where abacus writes `reasoning = true` and the three first-party `xiaomi-token-plan-*` hosts publish a thinking toggle. The distinction matters because `mergeBaseModel` merges the provider entry over the lab entry, so omitting the field defers to the lab while writing `false` overrides it. There is no narrower version worth keeping: when the lab says nothing the inherited value is already absent, so the rule only ever bites on the routes it gets wrong. Back to 13 skips. Those six are honest -- the route is skipped because the catalog contradicts itself, which is the signal that sends the fix to the endpoint instead of burying it here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gateway publishes two interfaces and they answer different questions. `/api/v1/models` says a route is reachable here; the canon projection at `/model-data/index.json` says what AIHubMix has actually verified about the model behind it. A catalog entry needs the second, so the model list is now read through canon rather than on its own. Canon covers 351 models against the list's 409, and the 117 it leaves out are the long tail the list describes worst: `Qwen/QwQ-32B`, which has no non-thinking mode, `AiHubmix-Phi-4-mini-reasoning`, `codex-mini-latest` and the whole `qwen3-*` hybrid-thinking family all report no `reasoning` flag at all. Reading that silence as a statement was the mistake the previous commit reverted; this stops the adapter from having to read it. Nothing moves today: all 106 creates, all 66 updates and all 66 files already on disk are covered, so the dry run is unchanged at 106/66. The filter is a ratchet, not a cleanup — it holds the line when the list grows a route canon has not reached yet. Sits in parseModels beside the route-variant filter and drops silently for the same reason: an uncovered route is not a gap a contributor here can close, so it should not raise a skip notice or a missing-model issue. Covered IDs are compared exactly; both registries are generated from the same gateway catalog and all 292 of today's overlaps match without case folding. A failed canon request throws instead of syncing ungated, because carrying on would publish exactly the routes the gate exists to hold back. Nothing is written on a throw, and `deleteMissing: false` means a gated-out route never costs a file either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three review findings, all in how the leading comment block is derived. The header is authoritative, so `composeHeader` strips any line opening `# Toggle:`, `# Effort:` or `# Budget:` on the grounds that the derived block restates it. It only ever derived the toggle. An effort or budget opening was therefore deleted with nothing put back, leaving the option row on the file and its field name nowhere -- `deepseek-v4-pro-0813` and `qwen3.7-flash` each lost one that way. Derive a block per control the file actually authors, and read the effort levels off the row they document so the two cannot drift. The toggle block also named `$.enable_thinking` alone. A control on this gateway has no single wire path: the same off state is reachable from whichever SDK dialect the caller speaks. Name one path per protocol, following what `providers/aihubmix/provider.toml` records for each surface. `-reasoning` is the one affix the gateway does not own outright. A lab can end a model's real name with it, and `AiHubmix-Phi-4-mini-reasoning` is Microsoft's -- cataloged here as `providers/azure/models/phi-4-mini-reasoning.toml`. What makes the Grok routes a steering pair is that they come as a pair, so ask the catalog for the `-non-reasoning` half rather than trusting the word. `coding-` needs no such check: 32 routes carry it, every one a relay of a plain sibling the list also carries, so it joins `-free` as a prefix match and its five cards go with it. Two cards carried hand-written data the endpoint contradicts, which is why the sync kept rewriting them. `deepseek-v4-pro-0813` states effort `high|max` where both the endpoint and canon publish `low|high|max`; `qwen3.7-flash` states no effort row at all against seven published levels, and its `[limit]`/`[modalities]` overrides restate the base model wrongly -- 991_000 is the input-token ceiling, not the context window. Both now match the response and survive a second sync unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Action items
|
AIHubMix echoes the OpenAI chat protocol's whole `ReasoningEffort` enum for a route it holds no per-model levels for. The routes it lands on say so: of 409, 18 receive it, among them `grok-4-fast-non-reasoning` — whose name states it does not reason — with `default = none`, the pinned `gpt-5.2-high`/`-low` variants, the image route `gemini-3-pro-image`, and every Qwen 3.5/3.6/3.7 entry while Qwen 3.8 carries a real `low|medium|xhigh`. `default` stays route-specific throughout, so the host knows the route's setting and is stating the protocol in `values`. Live probing agrees. On `qwen3.7-flash` and `deepseek-v4-pro-0813`, only `none` is observable — it returns no reasoning tokens and an empty `reasoning_content` — while the six graded levels are reproducibly non-monotonic across two runs (`minimal` above `high`, `xhigh` lowest). A file copying that would publish a ladder no caller can steer with. So the list is dropped where it is exactly the protocol's seven, and the toggle and budget the same route publishes carry the reasoning surface alone. It is a shape rule over the response, not a per-model baseline: `qwen3.7-flash` lands on `toggle` + `budget_tokens`, matching `alibaba-cn` and `openrouter` without either being consulted, and a narrowed list of any length survives untouched. `deepseek-v4-pro-0813` keeps the `low|high|max` the host narrows for all seven DeepSeek routes; its note now records that `low` is not observably distinct and that `none` works but goes unpublished — both are the host's to fix, and the file follows what it publishes rather than restating a hand baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The note claimed DeepSeek documents `high|max` with low folded into high. It documents `Possible values: [low, high, max]`, folding only `medium` and `xhigh` into `high` (api-docs.deepseek.com, archived in canon 2026-08-13). `low` is a real published level, so the three values this file carries are the lab's own enum passed through, not a host-side widening. The single-prompt probe is kept as what it is — an observation about one prompt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Action items
|
…exceptions `sync.md` said a local file absent from one response is retained *and* opens a deduped GitHub issue. Only the first half is true: `issueModels` is built from missing remotes, skipped remotes and missing reasoning options, and a retained local path is in none of the three, so a rotated-out file surfaces only through `missingNotice`. State notice-only rather than promise automation that does not run. The route-filter block named `minimax-m2.7-highspeed` as an exception without saying why it is not a gap. It is not: `cc-minimax-m2.7-highspeed` and `mm-minimax-m2.7-highspeed` are both listed, match no affix in the filter, and declare `variant_of = minimax-m2.7-highspeed`, which resolves to the lab entry `models/minimax/MiniMax-M2.7-highspeed.toml` — a dry run against the live list writes each of them a card, so the filter drops the `coding-` price point and not the model. The MiMo V2.5 pair reads the same way: the list spells them `mimo-v2.5`/`mimo-v2.5-pro` with no `xiaomi-` prefix. The one pair that really goes uncarded is `mimo-v2-omni`/`mimo-v2-pro`, skipped for publishing no `reasoning_options` rather than by this filter, and that is now said outright. Comments and docs only; no behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Ran the four items down against the live listing and a real dry run. Two of the four hold; the two
[high] [high] MiMo V2.5 — not a loss. The live list carries no [medium] [low] The one pair that genuinely goes uncarded is |
Action items
|
…n over
The endpoint now states `interleaved` per wire protocol — `true` where the
channel exists but its carrier has no settled name, `{field}` where it does —
because the carrier is a property of the protocol shape rather than of the
model: `claude-opus-4-8` returns thinking blocks on `/v1/messages` and nothing
at all on the chat-completions path.
Which protocol a given model is spoken over is itself per-model, not per
provider: `@aihubmix/ai-sdk-provider` builds `claude-*` as an Anthropic
messages model, `gemini*`/`imagen*` as a Google generative model (except the
`-nothink`/`-search` routes, which it sends back down the OpenAI-compatible
path), and everything else as an OpenAI-compatible chat model. `wireProtocol`
transcribes that from `createChatModel`, so each model is read on its own face
and never on another model's.
Silence stays unknown rather than denial, the same reading the missing
`reasoning` flag gets: a model the endpoint says nothing about keeps whatever
the file authored, and a face the endpoint describes for other models is no
less silent about this one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Action items
|
|
The 1. Accepted values are 2. The effort mapping table ( There is one table, and it is not broken down per model:
Its only two footnotes are "(1) Thinking mode is enabled by default, with the default effort being 3. Where the stale baseline came from DeepSeek's docs used to carry a sentence restricting pro to 4. The repo already disagrees with itself while
That rule contradicts the two flash files sitting next to it, and contradicts the current mapping table ( So On the probe note in the header. The file records that a 2026-09-15 probe could not separate One correction on our side. The header line "Off is the toggle on this path; If maintainers want, I can also bring |
Resolves the only conflict, in packages/core/src/sync/index.ts: dev added the `aiand` provider (anomalyco#7040) at the head of the alphabetical registry while this branch added `aihubmix` in the same position. Both are kept, ordered `aiand` then `aihubmix`, across all three sites (import, the providers type, the providers object). No logic is involved — it is an insertion-order conflict. Checked the parts that merged cleanly but could still drift: `groups` is new on dev, and `aihubmix` is in `aggregators` where a gateway belongs, not in `direct`. sync.test.ts and sync.md merged without conflict. Tests on the merge result: 232 pass / 3 fail. The same 3 fail on a clean origin/dev checkout (Hyper reasoning inheritance, DeepInfra live modalities, LLM Gateway case variant), as do the tsc errors in sync.test.ts, so both are pre-existing on dev and not introduced here. Base has 194 pass / 3 fail, so this branch adds 38 passing tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable findings. |
The header claimed `none` was "a level only on the Responses face". It is not:
DeepSeek's chat-completions docs list it in the same set
("Possible values: [none, low, high, max]") and describe it as disabling
thinking mode, i.e. an off switch spelled as a level rather than a fourth
intensity. Off is already carried by `type = "toggle"`, so `none` stays out of
the effort values -- the data was right, the explanation was not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous run failed on an upstream 402 ("Insufficient account funds"
from opencode.ai/zen), not on anything in this PR, and the workflow only
fires on opened/reopened/synchronize/ready_for_review -- there is no
retry. Empty commit, no content change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Action items
|
Every AIHubMix test built its own translateModel context, so the `header` callback could have been wired nowhere and the suite would still be green. The new test drives the real runner against local fixtures: a hand-written note on disk has to survive a sync that rewrites the header block it sits under. It lives in its own file because a real sync installs module-level catalog state the other AIHubMix tests expect unset. Writing it surfaced a duplicate-modality bug: when the endpoint omits modalities, the fallback is two overlapping records -- the narrowing list already on the file and the lab entry it narrows -- and a shared entry was written twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks — the
|
|
No actionable findings. |
376dfaf to
a0823d3
Compare
|
No actionable findings. |
Adds a fully interface-driven sync adapter for AIHubMix, an aggregator that
relays ~408 LLM routes from 26 model labs. Everything is read from the public
catalog endpoint — no credentials, no per-model overrides in the adapter.
This PR ships the adapter only
An earlier revision carried the 299 generated
providers/aihubmix/models/*.tomlfiles alongside the adapter. That pushed the diff past GitHub's 300-file ceiling,
gh pr diff --patchansweredHTTP 406, andpr-reviewer.ymldied in itscontext step before the agent ever started — so no revision of the branch could
reach a review at all.
The catalog files are now dropped from the PR. Once the adapter merges,
sync-models.ymlregenerates them onautomation/sync-models-aihubmix, a branchthe reviewer workflow already skips by design. The diff here is 22 files — 5 source, 8 note moves, 8 alias deletions, and
sync.md.Answers to the review findings
togglealongside an effort list containingnoneis intentional, not a bugThe reviewer flagged models such as
glm-5.2that publish both{type = "toggle"}and{type = "effort", values = [..., "none", ...]}as"contradictory metadata" that the adapter should reconcile. It is neither
contradictory nor a defect in the source — it is the gateway's design, and this
repo already documents it.
AIHubMix is a multi-dialect gateway: the same route is reachable over the
OpenAI-compatible
/v1/chat/completionspath, the Anthropic-compatible/v1/messagespath, and the Gemini native path. Each dialect has its ownoff-switch, and the gateway maps whichever one the caller sends onto the
vendor's real control instead of rejecting it.
providers/aihubmix/provider.tomlon
devalready spells all four out:$.reasoning_effort = "none"|"minimal"|…|"xhigh";"none"disables$.thinking.type = "enabled"|"disabled"|"adaptive"$.generationConfig.thinkingConfig,thinkingBudget: 0off$.reasoning.effortSo one model genuinely has two independent ways to turn thinking off, which is
exactly
toggle+ an effort list containingnone. Verified live againstglm-5.2on 2026-09-11 with a fixed prompt:reasoning_effort: "none"enable_thinking: falsereasoning_effort: "minimal"reasoning_effort: "max"reasoning_effort: "turbo"(bogus)That last row is the point: an unrecognised effort is mapped to the nearest
supported tier rather than erroring, because callers arrive from many SDKs with
many vocabularies. Collapsing the two signals in the adapter would delete a real,
independently reachable control from the catalog.
(An earlier comment of mine on this PR said this shape "is being fixed at the
source". That was my own inference and it was wrong; it is documented, intended
behaviour. Corrected publicly in a later comment.)
Wire path for the toggle — fixed
The reviewer was right that
{type = "toggle"}on its own does not tell a callerwhich field to send, and that a sync rewrites the file whole and would erase a
hand-written header.
translateModelnow emits the header itself whenever themodel carries a toggle — the same shape the OpenRouter adapter uses — so the wire
path survives every rewrite instead of depending on a human re-adding it.
Intersecting a relay's options with its lab/peer baseline — declined
The suggestion was to narrow a relay's advertised tiers against what its lab
model or sibling routes advertise. That would put per-model knowledge back into
the adapter — the exact thing this rewrite removed (the
developer_id → labtable and the prefix/suffix strip list). The gateway's tier support is a property
of the gateway, not of the lab: AIHubMix deliberately accepts tiers the upstream
vendor does not name and maps them. An intersection would silently drop tiers
that demonstrably work. Where the endpoint is wrong, the fix belongs on the
endpoint; two such fixes have already shipped there during this PR.
What the interface-driven rewrite removed
The adapter originally landed 232 models, resolving relays to lab metadata with
a hand-maintained
developer_id → labtable plus a list of routing prefixes andsuffixes to strip. Both are gone. AIHubMix now serves
vendor,variant_ofand
open_weightson the endpoint itself, so nothing about a relay is inferredfrom its ID or mirrored in this repo any more — the adapter reads the catalog's
own answer. That change is what brings the catalog to 308.
base_modeldeveloper_identriesvendorfrom the endpointvariant_offrom the endpointopen_weightsHow models are matched to lab metadata
The endpoint answers both halves of the lookup itself:
vendornames the lab that built the model (292 of 408 routes).variant_ofnames the AIHubMix ID this entry is a routing variant of, andvariant_kindlabels it (pricing-tier26,channel-tier24,mode-preset18,deprecated-alias8).A relay is looked up under its own ID first, then under each
variant_ofhop,nearest first. Nearest-first matters:
qwen3.8-max-previewis declared avariant of
qwen3.8-maxand both are published lab models, so the relay factorsonto the preview it actually serves rather than onto the root of its chain.
Following a declared chain also reaches relays no string rule could have:
ox-alphazhipuai/glm-5.3-flashgrok-code-fast-1xai/grok-build-0.1cohere-command-acohere/command-a-03-2025grok-4-fast-reasoningxai/grok-4.3deprecated-alias: the old ID now routes to 4.3The one thing left in the adapter is
VENDOR_LABS, four entries for the labsthe two registries spell differently (
zhipu/zhipuai,moonshot/moonshotai,bytedance/bytedance-seed,meituan-longcat/meituan). It maps namespaces;no entry in it decides what a model is or which lab built it.
Lookups stay case-folded: AIHubMix lowercases every relay ID while labs keep
their own casing, so
minimax-m2has to reachminimax/MiniMax-M2.Dated release tags are deliberately left on.
gemini-2.5-pro-preview-06-05is apinned snapshot, not
google/gemini-2.5-pro— and the endpoint does not declareit a variant of one, which is now the actual test rather than a judgement call
in the adapter.
A resolved base means the entry inherits
open_weights,knowledge,licenseand the rest from
models/, and records only what actually differs — which iswhy 273 of 308 entries are three or four lines long.
Standalone entries are skipped, not guessed
A full catalog entry has to carry
release_date,open_weightsand both limitsitself. The adapter skips anything that has neither lab metadata to inherit
from nor those fields, and reports it through
skippedNoticerather thaninventing a value.
open_weightsused to block every standalone create because the endpoint didnot serve it. It now does, for 289 of 408 routes, which is what lets the adapter
create standalone entries at all. The guard also checks limits now: the endpoint
sends
max_output: 0for a ceiling it does not know, and without that checkthose models reach the writer and fail
ModelBasevalidation.What is still uncovered, entirely on the endpoint side:
vendor, so they cannot be factoredrelease_dateopen_weightsmax_output: 0Every one of these shrinks as AIHubMix backfills its own catalog. The adapter
needs no change for any of them.
Sentinel values in the source data
Two signals mean "unknown", not "zero" or "huge", and are read as absent so the
field falls through to the base model:
max_output: 0max_output >= context_lengthNo value is invented — the field is simply omitted and inherited.
Reasoning options
The endpoint carries an extra
defaultkey insidereasoning_options[]thatReasoningOptiondoes not accept, and two non-standard effort spellings,no_thinkandinstant. The adapter drops the unknown key and maps thespellings onto
none/minimal, filtering anything else outsideReasoningEffortValue. These are shape-level normalisations — vocabularymapping, not per-model judgement — and all four are reported upstream to
AIHubMix.
Deletion policy
deleteMissingisfalseandtrackMissingModelsistrue. AIHubMix rotatesroutes in and out; a transient absence should not delete a catalog entry. The
sourceIDhook returnsundefinedforretire_stage === "deprecated"soretired routes are marked, not removed.
Framework changes
Four, all small and all needed by something above:
formatTomlnow emitsinput_audio/output_audioinside cost tiers. Bothare already in
Cost, but were silently dropped when writing tiered pricing.translateModel's options gainheader(id), the leading comment block alreadyon the file.
authoritativeHeadersreplaces that block wholesale, so withoutreading it first a provider that owns its header cannot refresh the part it
generates without discarding the notes a human wrote around it.
trackMissingModelsis now settable on its own. It was only ever readalongside
skipCreates, so a provider that creates most models but skips theones it cannot write produced notices nobody acts on. The missing-model issue
text is reworded to cover that second reading.
normalizeModelSlugis exported fromopenrouter.ts, which already serves asthe shared helper module for the other provider adapters. Two more adapters
(
venice.ts,wandb.ts) hold byte-identical private copies; left alone here.Limits are resolved against the lab window, not just accepted
Three rules, in this order. Each was written because the previous revision got a
real route wrong.
A restatement is a ratio, not a direction. The endpoint quotes an accepted
204800 as 200000 and an accepted 1000000 as 1048576. Neither is the host stating
a different window. A decimal restatement of a binary window loses at most
1000/1024per K unit, so1000³/1024³— three nested unit swaps — is the floorof what a restatement can explain, and two limits within that floor of each other
are one window spelled twice. Checking only the narrowing side left 20 routes
writing an override that states no difference at all (
glm-5.3recording 1048576against a lab window of 1000000).
The lab entry is the spelling a restatement resolves to, and the provider
file's own value only where the lab states no such key. Matching the lab lets
factoring drop the key entirely; resolving to whatever the file happens to hold
would pin that spelling forever —
qwen3.7-flashcarries 991000, which is onlyan imprecise way of writing the lab's 1000000. Resolving file-first preserved 10
such overrides,
claude-opus-4-8among them still recording 200000/32000 againsta 1000000/128000 lab window. That one is the finding from review: the endpoint
agrees with the lab there, and the narrowing number was the file's.
Then clamp to the lab's window — a relay cannot serve a wider one than the
model it relays; the window is the model's property and a host can only restrict
it. The clamp runs after the resolution rather than instead of it, because an
endpoint quoting back the same stale ceiling the file already holds resolves to
that number and only a later clamp catches it.
grok-4.5is exactly this: thefile held 1000000 for both limits against a 500000 lab window, and the endpoint
quotes that same 1000000.
Past the two
max_outputsentinels, 14 routes quote a window wider than theirlab entry's (
qwen3.8-2.4t-a95bat 1000000 of 262144,gemma-4-31b-itat 131100of 32768). All are reported upstream to AIHubMix; where the lab entry is the
stale side,
models/is where that gets corrected. After all three rules, thefactored files write 7 limit keys total — every one a genuine host cap, none
byte-identical to its lab entry.
Headers keep human notes and never outlive their block
authoritativeHeadersreplaces the leading block on every sync, so what countsas "the block" has to be exact in both directions.
with (
# Toggle:,# Effort:,# Budget:,# Off is effort— the openingsAGENTS.mdprescribes), not by whether it mentions a field path or the docshost. Keying on the substring would also delete lines that merely contain one:
two files state a wire path together with a dated live test the response cannot
reproduce. A second statement of the same path costs nothing; a deleted
verification date cannot be recovered. Matched on the trimmed line, or an
indented
# Toggle:outlives the block it documented.restatement of the block, so they go whether or not a block replaces them. Only
the docs link survives as a note, and only where no derived block restates it.
Keeping the wire paths when nothing was derived is what left a route
advertising a toggle it no longer had — the block vanished, its tail survived
as a "note", and no later sync could tell the difference, so the file never
self-corrected.
AGENTS.mdrequires these notes above the first key, since a sync keeps onlythe leading block and drops every comment below it. Eight files in this
provider carried theirs mid-body; they are moved up here, which is 8 of the 22
files in this diff. Without the move the next sync deletes them silently.
Display names
The endpoint's label is recorded only where the relay is not that lab model under
other punctuation — its bare ID, normalised, differs from the base model's
slug. Bare, because that is what resolved the base model in the first place
(
Qwen/QwQ-32Breachesqwen/qwq-32b); normalising the namespaced form matchesnothing, and each of the 10 namespaced routes would take a redundant storefront
override the moment its lab file lands.
This keeps
coding-glm-4.6-freereading "Coding GLM 4.6 (free)" rather than thebare "GLM-4.6" it would share with two other routes, while entries differing only
in punctuation (
GLM 5.3against the lab'sGLM-5.3) defer to the lab and writenothing. A name already on the file outranks both — four files spell their model
the way its lab does (
MiMo-V2.5) where the endpoint sends a storefrontMimo V2.5— and handing it through stays correct because factoring drops a namethe lab states identically.
A blank label is not a name.
ModelBase.nameismin(1), so writing one throughaborts the entire provider's sync at validation rather than skipping the
field. 0 of the 409 live routes send one today, which is exactly why it needed a
test rather than a live check.
Verification
bun run validate— exit 0bun test packages/core/test/sync.test.ts— 220 pass, 2 fail (bothpre-existing on
dev: DeepInfra modalities and an LLM Gateway case-variantassertion that only fails on case-insensitive filesystems)
reverted, checked one at a time
bunx tsc --noEmit -p packages/core— no aihubmix errors129 created, 69 updatedthen0 created, 0 updated, 0 removedwith 198 unchanged (idempotent)dev— no provider other than aihubmix changesReview round: two fixed, two answered
Empty
reasoning_optionson creates — fixedA route the endpoint reports as reasoning while publishing no
reasoning_optionsreached the runner with the field unset, and the runnerreads that on a reasoner as "no caller control" and stamps
[]— whichAGENTS.mdrule 4 forbids using for uncertainty. The guard meant to prevent itcompares against the base model's options, and
ModelMetadataBasehas noreasoning_optionsfield, so that comparison is alwaysundefinedand theguard never fires.
The adapter now throws
MissingReasoningOptionsErroron the two paths thatactually write, the same as the Cloudflare adapter. 13 creates in the
current listing are skipped and surface as missing models instead of being
published with an invented "no controls". An authored
[]already on a file isa human stating the host exposes no control, and is carried through untouched
(
grok-build-0.1ondev).Stale
deprecatednever cleared — fixedstatusonly ever wrotedeprecatedand otherwise handed the file's valueback, so a route that came back from retirement kept the mark for good.
retire_stagerides on every route (407 active, 2 deprecated), so it isauthoritative about retirement — and only about retirement. A non-deprecated
stage now clears a
deprecatedstatus;alphaandbetaare left alonebecause the endpoint says nothing about either.
Bare
budget_tokens— not a defectRaised as "inventing unbounded controls that shadow real bounds". Three
reasons it is neither:
budget_tokensentries itself. The adapter relays a declaration; it does not synthesise one.
ModelMetadatahas noreasoning_optionsfield, soa lab entry cannot carry a budget range for a provider file to override. A
first-party entry for the same model states that host's API contract, which
does not transfer — AIHubMix's Anthropic-compatible path rejects a manual
budget_tokenson the Opus tier, which the affected files record in theirheaders, so copying
min = 1024across would be wrong.AGENTS.md's Qwen3.5 Plus example authors{ type = "budget_tokens" }bare with the wire path in the header comment,and 256 of the 354 budget entries under
providers/are bare(
requesty138,alibaba23,neuralwatt16). Authoredmin/maxon the8 aihubmix files that have them are preserved, not replaced.
Copying bounds from a peer provider would also put per-model knowledge back
into the adapter, which is what this rewrite exists to remove.
Deleted channel aliases — no gap
Flagged as deletions into a catalog gap. Each of the 8 dropped alias files is
absent from the model list while the ID it routes to is on it, so every
one is replaced by an entry the first sync writes. The gap is visible only
because this PR ships the adapter without its generated files.
Note for maintainers on the sync gate
aihubmix is not in
REVIEWED_REASONING_PROVIDERS, and most of its routes arereasoning models, so the hourly sync PR will be classified unsafe on nearly
every run and wait for a human. That is the right default for a new adapter —
happy to leave it there until it has a track record.
🤖 Generated with Claude Code